﻿//-----------------------------------------------------------------------------------------
// <copyright file="BlipService.cs" company="Jakub Florczyk (www.jakubflorczyk.pl)">
//      Copyright © 2009 Jakub Florczyk (www.jakubflorczyk.pl)
//      http://blipnet.codeplex.com/
// </copyright>
//-----------------------------------------------------------------------------------------

namespace BlipNet
{
    using System;
    using System.Globalization;
    using System.IO;
    using System.Net;
    using System.Text;
    using Newtonsoft.Json;

    /// <summary>
    /// Delegator uploadu.
    /// </summary>
    /// <param name="length">Ilość danych.</param>
    /// <param name="position">Aktualna pozycja.</param>
    public delegate void UploadProgress(int length, int position);

    /// <summary>
    /// Blip serwis.
    /// </summary>
    public partial class BlipService
    {
        /// <summary>
        /// Adres api.
        /// </summary>
        private const string ApiUri = "http://api.blip.pl";

        /// <summary>
        /// Domyślna nazwa aplikacji.
        /// </summary>
#if !PocketPC
        private const string DefaultApplicationName = "BlipNet";
#else
        private const string DefaultApplicationName = "BlipNetCF";
#endif

        /// <summary>
        /// Login użytkownika.
        /// </summary>
        private string login;

        /// <summary>
        /// Hasło użytkownika.
        /// </summary>
        private string password;

        /// <summary>
        /// Nazwa aplikacji.
        /// </summary>
        private string applicationName = DefaultApplicationName;        

        /// <summary>
        /// Tworzy nową instancję klasy BlipService z pustym loginem i hasłem.
        /// </summary>
        public BlipService()
        {
        }        

        /// <summary>
        /// Tworzy nową instancję klasy BlipService z podanym loginem i hasłem.
        /// </summary>
        /// <param name="login">Login użytkownika.</param>
        /// <param name="password">Hasło użytkownika.</param>
        public BlipService(string login, string password) 
            : this()
        {
            this.login = login;
            this.password = password;            
        }

        /// <summary>
        /// Login użytkownika.
        /// </summary>
        public string Login
        {
            get { return this.login; }
            set { this.login = value; }
        }

        /// <summary>
        /// Hasło użytkownika.
        /// </summary>
        public string Password
        {
            get { return this.password; }
            set { this.password = value; }
        }

        /// <summary>
        /// Nazwa aplikacji.
        /// </summary>
        public string ApplicationName
        {
            get { return this.applicationName; }
            set { this.applicationName = value; }
        }

        /// <summary>
        /// Deserializacja z Json.
        /// </summary>
        /// <typeparam name="T">Typ entity.</typeparam>
        /// <param name="s">String do deserializacji.</param>
        /// <returns>Instancja entity.</returns>
        private static T Deserialize<T>(string s) where T : class
        {
            T t = null;

            using (StringReader sr = new StringReader(s))
            {
                JsonSerializer serializer = new JsonSerializer();
                serializer.MissingMemberHandling = MissingMemberHandling.Ignore;
                t = (T)serializer.Deserialize(sr, typeof(T));
            }

            return t;
        }

        /// <summary>
        /// Deserializacja tablicy z Json.
        /// </summary>
        /// <typeparam name="T">Typ entity.</typeparam>
        /// <param name="s">String do deserializacji.</param>
        /// <returns>Tablica instancji entity.</returns>
        private static T[] DeserializeMany<T>(string s) where T : class
        {
            T[] t = null;

            using (StringReader sr = new StringReader(s))
            {
                JsonSerializer serializer = new JsonSerializer();
                serializer.MissingMemberHandling = MissingMemberHandling.Ignore;
                t = (T[])serializer.Deserialize(sr, typeof(T[]));
            }

            return t;
        }

        /// <summary>
        /// Odczyt odpowiedzi.
        /// </summary>
        /// <param name="response">HttpWebResponse odpowiedzi.</param>
        /// <returns>String odpowiedzi.</returns>
        private static string ReadResponse(HttpWebResponse response)
        {
            string s = string.Empty;

            using (StreamReader sr = new StreamReader(response.GetResponseStream()))
            {
                s = sr.ReadToEnd();
                sr.Close();
            }
            
            return s;
        }        

        /// <summary>
        /// Żądanie typu "GET".
        /// </summary>
        /// <typeparam name="T">Typ entity.</typeparam>
        /// <param name="query">Zapytanie serwera.</param>
        /// <returns>Instancję entity.</returns>
        private T RequestGet<T>(string query) where T : class
        {
            HttpWebRequest request;
            HttpWebResponse response;

            request = this.CreateRequest(query, "GET");
            response = (HttpWebResponse)request.GetResponse();

            string responseString = ReadResponse(response);

            response.Close();
            request.Abort();
            response = null;
            request = null;

            T t = Deserialize<T>(responseString);

            return t;
        }

        /// <summary>
        /// Żądanie typu "GET" zwracające tablicę.
        /// </summary>
        /// <typeparam name="T">Typ entity.</typeparam>
        /// <param name="query">Zapytanie serwera.</param>
        /// <returns>Tablica instancji entity.</returns>
        private T[] RequestGetMany<T>(string query) where T : class
        {
            HttpWebRequest request;
            HttpWebResponse response;

            request = this.CreateRequest(query, "GET");
            response = (HttpWebResponse)request.GetResponse();

            string responseString = ReadResponse(response);

            response.Close();
            request.Abort();
            response = null;
            request = null;

            T[] t = DeserializeMany<T>(responseString);

            return t;
        }

        /// <summary>
        /// Żądanie typu "DELETE".
        /// </summary>
        /// <param name="query">Zapytanie serwera.</param>
        private void RequestDelete(string query)
        {
            HttpWebRequest request;
            HttpWebResponse response;

            request = this.CreateRequest(query, "DELETE");
            response = (HttpWebResponse)request.GetResponse();

            response.Close();
            request.Abort();
            response = null;
            request = null;
        }

        /// <summary>
        /// Żądanie typu "PUT".
        /// </summary>
        /// <param name="query">Zapytanie serwera.</param>
        private void RequestPut(string query)
        {
            // przygotowanie requesta
            HttpWebRequest request;
            HttpWebResponse response;

            request = this.CreateRequest(query, "PUT");
            request.ContentLength = 0; // musimy ustawić zero, inaczej serwer zwraca błąd
            response = (HttpWebResponse)request.GetResponse();

            response.Close();
            request.Abort();
            response = null;
            request = null;
        }

        /// <summary>
        /// Żądanie typu "POST".
        /// </summary>
        /// <param name="query">Zapytanie serwera.</param>
        /// <param name="data">Tablica parametrów.</param>
        private void RequestPost(string query, params string[] data)
        {
            // przygotowanie danych
            string amp = string.Empty;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < data.Length; i += 2)
            {
                sb.Append(amp);
                sb.Append(data[i]);
                sb.Append("=");
                sb.Append(data[i + 1]);
                amp = "&";
            }

            byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());

            // przygotowanie requesta
            HttpWebRequest request;
            HttpWebResponse response;

            request = this.CreateRequest(query, "POST");
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = bytes.Length;

            using (Stream requestStream = request.GetRequestStream())
            {
                // zapis do streamu http
                requestStream.Write(bytes, 0, bytes.Length);
                requestStream.Close();
            }

            response = (HttpWebResponse)request.GetResponse();

            response.Close();
            request.Abort();
            response = null;
            request = null;
        }

        /// <summary>
        /// Żądanie typu "POST".
        /// </summary>
        /// <typeparam name="T">Typ entity.</typeparam>
        /// <param name="query">Zapytanie serwera.</param>
        /// <param name="data">Tablica parametrów.</param>
        /// <returns>Instancję entity.</returns>
        private T RequestPost<T>(string query, params string[] data) where T : class
        {
            // przygotowanie danych
            string amp = string.Empty;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < data.Length; i += 2)
            {
                sb.Append(amp);
                sb.Append(data[i]);
                sb.Append("=");
                sb.Append(data[i + 1]);
                amp = "&";
            }

            byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());

            // przygotowanie requesta
            HttpWebRequest request;
            HttpWebResponse response;

            request = this.CreateRequest(query, "POST");
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = bytes.Length;

            using (Stream requestStream = request.GetRequestStream())
            {
                // zapis do streamu http
                requestStream.Write(bytes, 0, bytes.Length);
                requestStream.Close();
            }

            response = (HttpWebResponse)request.GetResponse();

            string responseString = ReadResponse(response);

            response.Close();
            request.Abort();
            response = null;
            request = null;

            T t = Deserialize<T>(responseString);

            return t;
        }

        /// <summary>
        /// Żądanie typu "POST" uploadujące.
        /// </summary>
        /// <param name="query">Zapytanie serwera.</param>
        /// <param name="name">Nazwa parametru.</param>
        /// <param name="fileName">Nazwa pliku.</param>
        /// <param name="stream">Strumień danych.</param>
        /// <param name="progress">Delegator postępu.</param>
        /// <param name="data">Tablica parametrów.</param>        
        private void RequestUpload(string query, string name, string fileName, Stream stream, UploadProgress progress, params string[] data)
        {
            long length = 0;
            MemoryStream memoryStream = new MemoryStream();

            // przygotowanie boundary
            string boundary = "---------------------" + DateTime.Now.Ticks.ToString("x", CultureInfo.InvariantCulture).Substring(0, 12);

            // boundary bufor
            byte[] boundaryBuffer = Encoding.ASCII.GetBytes("--" + boundary + "\r\n");

            // parametry
            if (data != null)
            {
                for (int i = 0; i < data.Length; i += 2)
                {
                    byte[] paramBuffer = Encoding.UTF8.GetBytes("--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + data[i] + "\"\r\n\r\n" + data[i + 1].ToString(CultureInfo.InvariantCulture) + "\r\n");
                    memoryStream.Write(paramBuffer, 0, paramBuffer.Length);
                    length += paramBuffer.Length;
                }
            }
           
            // zapis do pamięci boundary
            memoryStream.Write(boundaryBuffer, 0, boundaryBuffer.Length);
            length += boundaryBuffer.Length;

            // przygotowanie headera streamu
            string headerStream = "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + fileName + "\"\r\nContent-Type: image/jpeg\r\n\r\n";
            byte[] headerStreamBuffer = Encoding.UTF8.GetBytes(headerStream);

            // zapis do pamięci headera
            memoryStream.Write(headerStreamBuffer, 0, headerStreamBuffer.Length);
            length += headerStreamBuffer.Length;

            // zapis pliku do pamięci
            byte[] buffer = new byte[4096];
            int bytesRead = 0;
            while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
            {
                memoryStream.Write(buffer, 0, bytesRead);
                length += bytesRead;
            }            

            // kończące request boundary
            boundaryBuffer = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--");
            memoryStream.Write(boundaryBuffer, 0, boundaryBuffer.Length);
            length += boundaryBuffer.Length;

            // przygotowanie requesta
            HttpWebRequest request;
            HttpWebResponse response;

            request = this.CreateRequest(query, "POST");
            request.ContentType = "multipart/form-data; boundary=" + boundary;
            request.KeepAlive = true;
            request.ContentLength = length;

            using (Stream requestStream = request.GetRequestStream())
            {
                // zapis do streamu http
                int position = 0;
                memoryStream.Seek(0, SeekOrigin.Begin);
                while ((bytesRead = memoryStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    requestStream.Write(buffer, 0, bytesRead);

                    if (progress != null)
                    {
                        position += bytesRead;
                        progress((int)length, position);
                    }
                }

                stream.Close();
                requestStream.Close();
            }

            memoryStream.Close();
            memoryStream.Dispose();

            response = (HttpWebResponse)request.GetResponse();

            response.Close();
            request.Abort();
            response = null;
            request = null;
        }

        /// <summary>
        /// Utworzenie żądania.
        /// </summary>
        /// <param name="query">Zapytanie serwera.</param>
        /// <param name="method">Metoda żądania.</param>
        /// <returns>Żądanie HttpWebRequest.</returns>
        private HttpWebRequest CreateRequest(string query, string method)
        {
            HttpWebRequest request;
            request = (HttpWebRequest)HttpWebRequest.Create(ApiUri + query);
            request.Method = method;
            request.Accept = "application/json";
            request.UserAgent = this.applicationName;
            request.Headers.Add("X-Blip-API", "0.02");

            if (!String.IsNullOrEmpty(this.login) && !String.IsNullOrEmpty(this.password))
            {
                request.Headers.Add("Authorization", String.Format(CultureInfo.InvariantCulture, "Basic {0}", Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(String.Format(CultureInfo.InvariantCulture, "{0}:{1}", this.login, this.password)))));
            }

            request.KeepAlive = false;
            request.AllowWriteStreamBuffering = true;
            return request;
        }
    }
}
